package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
	"github.com/gin-gonic/gin"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cast"
	"github.com/spf13/viper"
)

type Service struct {
	Targets []string          `json:"targets" binding:"required"`
	Labels  map[string]string `json:"labels"`
}

type Response struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data"`
}

type ServiceList []Service
type config map[string]ServiceList

var (
	g_services map[string]ServiceList
	g_mtx      sync.RWMutex
)

func UpdateConfig(filename string) {
	viper.SetConfigFile(filename)
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatal(fmt.Sprintf("Read config file fail: %s", err.Error()))
	}

	//Replace environment variables
	err = viper.ReadConfig(strings.NewReader(os.ExpandEnv(string(content))))
	if err != nil {
		log.Fatal(fmt.Sprintf("Parse config file fail: %s", err.Error()))
	}
	tmp := map[string]ServiceList{}

	if err := viper.Unmarshal(&tmp); err == nil && len(tmp) > 0 {
		g_mtx.Lock()
		defer g_mtx.Unlock()
		g_services = tmp
		log.Warn("成功加载新配置")
	} else {
		log.Error("配置文件读取错误:%v", err)
	}

}

func Configs(name string) ServiceList {
	g_mtx.RLock()
	defer g_mtx.RUnlock()
	if l, ok := g_services[name]; ok {
		return l
	}
	return ServiceList{}
}
func AddConfig(filename, name string, cfg Service) error {
	g_mtx.Lock()
	defer g_mtx.Unlock()
	g_services[name] = append(g_services[name], cfg)
	viper.Set(name, g_services[name])
	return viper.WriteConfigAs(filename)
}

func main() {
	var (
		port     uint = 8080
		filename      = "etc/settings.yaml"
	)
	flag.StringVar(&filename, "f", "etc/settings.yaml", "配置文件")
	flag.UintVar(&port, "p", 8080, "本地端口号")
	flag.Parse()
	filename, _ = filepath.Abs(filename)
	UpdateConfig(filename)

	viper.OnConfigChange(func(in fsnotify.Event) {
		if in.Op == fsnotify.Remove {
			log.Printf("Removing configuration:%v", in.String())
		}
		UpdateConfig(filename)
	})
	viper.WatchConfig()

	router := gin.Default()
	router.GET("/services/:name", func(ctx *gin.Context) {
		name := ctx.Param("name")
		ctx.AbortWithStatusJSON(http.StatusOK, Configs(name))
	})
	router.PUT("/register/:name", func(ctx *gin.Context) {
		tmp := Service{}
		//if data, err := ioutil.ReadAll(ctx.Request.Body); err == nil {
		//	print(string(data))
		//}
		if err := ctx.ShouldBindYAML(&tmp); err != nil {
			ctx.AbortWithStatusJSON(http.StatusBadRequest, Response{
				Code: http.StatusBadRequest,
				Msg:  err.Error(),
			})
			return
		}
		name := ctx.Param("name")
		if err := AddConfig(filename, name, tmp); err != nil {
			ctx.AbortWithStatusJSON(http.StatusInternalServerError, Response{
				Code: http.StatusInternalServerError,
				Msg:  err.Error(),
			})
			return
		}
		ctx.AbortWithStatusJSON(http.StatusOK, Response{
			Code: http.StatusOK,
		})
		return
	})
	_ = router.Run(":" + cast.ToString(port))
	return
}
